﻿using Microsoft.WindowsAzure.Storage.Blob;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.AccessControl;
using System.Security.Principal;
using System.Threading;
using System.Web;

namespace Wams.Server
{
    public class WamsContent
    {
        private static object _allInstanceContentSync = new object();
        private static List<WamsContent> _allInstanceContent = new List<WamsContent>();
        public IWamsApplication ParentApp = null;
        public string Source = null;
        public int Index = 0;

        public string InstallPath
        {
            get
            {
                return string.Format("{0}\\{1}{2}", 
                    WamsCore.InstallFolder, 
                    Source, 
                    Index == 0 ? "": string.Format("({0})", Index.ToString()));
            }
        }
        

        public string FullBlobSourcePath
        {
            get
            {
                return string.Format("wams/{0}", Source);
            }
        }

        public WamsContent(string blobSource, IWamsApplication parentApp)
        {
            Source = blobSource;
            ParentApp = parentApp;
        }

        public bool Synchronise()
        {
            Logger.Log(LogLevel.Info, "Sync Content: " + this.Source);
            try
            {
                CheckUniqueIndex();

                //SyncDirectory(FullBlobSourcePath, InstallPath);
                Dictionary<string, DateTime> filesList = new Dictionary<string, DateTime>();
                GetFilesList(filesList, InstallPath, "");
                Dictionary<string, DateTime> blobsList = GetFilesFromBlob(FullBlobSourcePath, Source);

                WamsContentSyncTodo todo = FilterFilesForChanges(filesList, blobsList, Source);
                foreach (string file in todo.FilesToDelete)
                {
                    try { File.Delete(string.Format("{0}\\{1}", InstallPath, file)); }
                    catch { /* Some files might be readonly or in use. */ }
                }
                foreach (string file in todo.FilesToDownload)
                {
                    DownloadFile(InstallPath, file, Source);
                }

                // Setting site file permissions.
                if (ParentApp != null) SetFilePermissions("IIS AppPool", ParentApp.GetAppPoolName());

                return true;
            }
            catch (Exception ex)
            {
                Logger.Log(LogLevel.Error, "Ex while syncing content for " + this.Source + ":\r\n" + ex.ToString());
                return false;
            }
        }

        private void CheckUniqueIndex()
        {
            lock (_allInstanceContentSync)
            {
                if (!_allInstanceContent.Contains(this))
                {
                    var contentsWithSameSource = _allInstanceContent.Where(x => x.Source.ToLower() == this.Source.ToLower());
                    bool found = true;
                    while (found)
                    {
                        found = false;
                        foreach (var content in contentsWithSameSource)
                        {
                            if (content.Index == this.Index)
                            {
                                Index++;
                                found = true;
                                break;
                            }
                        }
                    }
                    _allInstanceContent.Add(this);
                }
            }
        }

        private static WamsContentSyncTodo FilterFilesForChanges(Dictionary<string, DateTime> physicalFiles, Dictionary<string, DateTime> blobFiles, string sourceName)
        {
            WamsContentSyncTodo result = new WamsContentSyncTodo();
            foreach (string file in blobFiles.Keys)
            {
                string blobFileName = file.Substring(sourceName.Length + 1).Replace("/","\\");
                if (!physicalFiles.ContainsKey(blobFileName)) result.FilesToDownload.Add(file);
                else
                {
                    if (physicalFiles[blobFileName] < blobFiles[file]) result.FilesToDownload.Add(file);
                    physicalFiles.Remove(blobFileName);
                }
            }

            foreach (string oldFile in physicalFiles.Keys) result.FilesToDelete.Add(oldFile);
            
            return result;
        }

        private static void DownloadFile(string installPath, string blobPath, string sourceName)
        {
            CloudBlobClient blobClient = WamsCore.MainStorageAccount.CreateCloudBlobClient();
            CloudBlobContainer container = blobClient.GetContainerReference(WamsCore.ContainerName);
            CloudBlockBlob blob = container.GetBlockBlobReference(blobPath);
            string filePath = string.Format("{0}\\{1}", installPath, blobPath.Substring(sourceName.Length + 1).Replace('\\', '/'));
            if (!Directory.Exists(Path.GetDirectoryName(filePath))) Directory.CreateDirectory(Path.GetDirectoryName(filePath));
            if (File.Exists(filePath)) File.Delete(filePath);
            using (FileStream fs = File.OpenWrite(filePath))
            {
                blob.DownloadToStream(fs);
            }
        }

        private static Dictionary<string, DateTime> GetFilesFromBlob(string fullBlobSourcePath, string sourceName)
        {
            Dictionary<string, DateTime> result = new Dictionary<string, DateTime>();
            CloudBlobClient blobClient = WamsCore.MainStorageAccount.CreateCloudBlobClient();

            foreach (IListBlobItem item in blobClient.ListBlobs(fullBlobSourcePath + "/", true, BlobListingDetails.Metadata))
            {
                if (!(item is CloudBlockBlob)) continue;
                result.Add(((CloudBlockBlob)item).Name, ((CloudBlockBlob)item).Properties.LastModified.Value.UtcDateTime);
            }
            return result;
        }

        private static void GetFilesList(Dictionary<string, DateTime> totalFilesList, string installPath, string physicalPath)
        {
            string fullPath = string.Format("{0}{1}", installPath, physicalPath);
            if (!Directory.Exists(fullPath)) return;

            foreach (string file in Directory.GetFiles(fullPath))
            {
                FileInfo fInfo = new FileInfo(file);
                totalFilesList.Add(file.Substring(installPath.Length+1), fInfo.LastWriteTimeUtc);
            }
            foreach (string directory in Directory.GetDirectories(fullPath))
            {
                GetFilesList(totalFilesList, installPath, directory.Substring(installPath.Length));
            }
        }

        //private static void SyncDirectory(string blobSourcePath, string installPath)
        //{
        //    CloudBlobClient blobClient = WamsCore.MainStorageAccount.CreateCloudBlobClient();
        //    CloudBlobContainer container = blobClient.GetContainerReference(WamsCore.ContainerName);

        //    try
        //    {
        //        if (Directory.Exists(installPath))
        //        {
        //            System.IO.DirectoryInfo dir = new DirectoryInfo(installPath);

        //            foreach (FileInfo file in dir.GetFiles()) file.Delete();
        //            foreach (DirectoryInfo innerDir in dir.GetDirectories()) innerDir.Delete(true);
        //        }
        //        Directory.CreateDirectory(installPath);
        //    }
        //    catch { /* Delete might fail if files are in use. */ }

        //    foreach (IListBlobItem item in blobClient.ListBlobs(blobSourcePath + "/"))
        //    {
        //        string itemName = item.Uri.ToString().Substring(item.Uri.ToString().TrimEnd('/').LastIndexOf('/') + 1).TrimEnd('/');
        //        if (item is CloudBlockBlob)
        //        {
        //            using (FileStream fs = File.OpenWrite(string.Format("{0}\\{1}", installPath, itemName)))
        //            {
        //                ((CloudBlockBlob)item).DownloadToStream(fs);
        //            }
        //        }
        //        else if (item is CloudBlobDirectory) SyncDirectory(string.Format("{0}/{1}", blobSourcePath, itemName), string.Format("{0}\\{1}", installPath, itemName));
        //    }
        //}

        public void SetFilePermissions(string domainName, string identityName)
        {
            // Ensuring that the application pool identity has the necessary read rights on the site folder.
            int remainingRetries = 5;
            bool successful = false;
            while (!successful && remainingRetries > 0)
            {   // We're using a retry system because at times, IIS would not have created the application pool identity yet.
                remainingRetries--;
                try
                {
                    SetDirAccess(this.InstallPath, domainName, identityName, System.Security.AccessControl.FileSystemRights.Read);
                    successful = true;
                }
                catch (Exception ex)
                {
                    Logger.Log(LogLevel.Error, string.Format("Ex while trying to give read file rights to {0}\\{1} on {4}. Remaining {2} retries:\r\n{3}",
                        domainName, 
                        identityName, 
                        remainingRetries,
                        ex.ToString(),
                        this.InstallPath));
                    Thread.Sleep(200);
                }
            }
            if (!successful) Logger.Log(LogLevel.Error, string.Format("Gave up setting file rights to {0}\\{1} on {2}.", domainName, identityName, this.InstallPath));
            else Logger.Log(LogLevel.Info, string.Format("File rights for {0}\\{1} on {2} done.", domainName, identityName, this.InstallPath));
        }

        public static void SetDirAccess(string path, string domainName, string identityName, FileSystemRights rights)
        {
            path = Environment.ExpandEnvironmentVariables(path).TrimEnd('\\');

            var dirSec = Directory.GetAccessControl(path);
            dirSec.AddAccessRule(new FileSystemAccessRule(domainName + "\\" + identityName, rights, InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit, PropagationFlags.None, AccessControlType.Allow));
            Directory.SetAccessControl(path, dirSec);
        }
        public static void SetDirAccess(string path, SecurityIdentifier identity, FileSystemRights rights)
        {
            path = Environment.ExpandEnvironmentVariables(path).TrimEnd('\\');

            var dirSec = Directory.GetAccessControl(path);
            dirSec.AddAccessRule(new FileSystemAccessRule(identity, rights, InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit, PropagationFlags.None, AccessControlType.Allow));
            Directory.SetAccessControl(path, dirSec);
        }

        public void Delete()
        {
            Logger.Log(LogLevel.Info, string.Format("Deleting content {0}", InstallPath));
            
            try { Directory.Delete(InstallPath, true); }
            catch (Exception ex)
            {
                Logger.Log(LogLevel.Error, string.Format("Could not delete folder {0}: {1}",InstallPath, ex.ToString()));
            }

            lock (_allInstanceContentSync)
            {
                _allInstanceContent.Remove(this);
            }
        }
    }
}